use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use serde::{Deserialize, Serialize};
use std::cmp::max;

static DX: [i32; 4] = [1, 0, -1, 0];
static DY: [i32; 4] = [0, 1, 0, -1];
// static RAMP_SIDE_IN: [Ramp; 4] = [Ramp::Up, Ramp::Left, Ramp::Down, Ramp::Right];
static RAMP_SIDE_OUT: [Ramp; 4] = [Ramp::Down, Ramp::Right, Ramp::Up, Ramp::Left];

pub type GenResult<T> = std::result::Result<T, GenError>;

pub mod base;
pub use base::*;

pub mod terrain;
pub use terrain::*;

// pub mod terrain;
// pub use terrain::*;

pub mod parse;
pub use parse::*;

// pub mod water;
// pub use water::*;

pub mod object;
pub use object::*;

pub mod ramp;
pub use ramp::*;

use thiserror::Error;

#[derive(Error, Debug)]
pub enum GenError {
    #[error("Warning : No enough start points to choose at height : {0}")]
    NoEnoughStartPoint(i8),
    #[error("Warning : No enough area to fill at hight {0}")]
    NoEnoughArea(i8),
    #[error("Error : No block to set the player {0} !")]
    NoBlocksSetPlayer(i32),
    #[error("Error : No block to set the object {0} !")]
    NoBlocksSetObject(usize),
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Percent(pub u8, pub u8);

impl Percent {
    fn rand(&self, rng: &mut ChaCha8Rng) -> u8 {
        rng.gen_range(self.0..=self.1)
    }
    fn get(&self, input: usize, rng: &mut ChaCha8Rng) -> usize {
        max(
            1,
            ((input as f64) * (self.rand(rng) as f64) / 100.0) as usize,
        )
    }
}

#[derive(Debug, Clone, PartialEq, Copy)]
pub enum LandOrWater {
    Land,
    Water,
}

#[derive(Clone, PartialEq, Debug, Default, Copy)]
pub enum Ramp {
    Up,
    Down,
    Left,
    Right,
    #[default]
    None,
}
impl Into<usize> for Ramp {
    fn into(self) -> usize {
        debug_assert!(self != Ramp::None, "The ramp here should not be none !");
        match self {
            Ramp::Up => 2,
            Ramp::Down => 0,
            Ramp::Left => 3,
            Ramp::Right => 1,
            Ramp::None => {
                panic!()
            }
        }
    }
}

#[derive(Clone, PartialEq, Debug, Default, Copy)]
pub enum Captured {
    #[default]
    None,
    Ramp,
    PlayerSpawn,
}

#[derive(Debug, Clone)]
pub struct Block {
    pub is_land: LandOrWater,
    pub height: i8,
    pub ramp: Ramp,
    pub captured: Captured,
    pub close_to_low: bool,
    pub belong_to: usize,
}

type MapBase = Vec<Vec<Block>>;

pub struct MapGen {
    pub base: MapBase,
    pub rng: ChaCha8Rng,
    pub config: GenConfig,
    pub parsed: Option<Ra3Map>,
    pub templates: Vec<ObjectTemplate>,
    pub object_count: Vec<usize>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct ObjectTemplateOption {
    pub file: String,
    pub count: usize,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct GenConfig {
    pub block_size: usize,
    pub water_height: i32,
    pub default_height: i32,
    pub m: usize,
    pub n: usize,
    pub top_height: i8,
    pub high_ground: Percent,
    pub high_ground_area: Percent,
    pub ramp_to_area: Percent,
    pub lakes: Percent,
    pub lake_area: Percent,
    pub lake_ramps: Percent,
    pub players: i32,
    pub height_deff: i32,
    pub water_block_height: i32,
    pub random_seed: Option<u64>,
    pub enable_lake: bool,
    pub objects: Vec<ObjectTemplateOption>,
}

pub fn point_in_map((x, y): (i32, i32), (m, n): (usize, usize)) -> bool {
    x >= 0 && x < m as i32 && y >= 0 && y < n as i32
}
